package com.recSystem.Service.ServiceImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.recSystem.Entity.Converters.AuthorConverter;
import com.recSystem.Entity.Converters.PaperConverter;
import com.recSystem.Entity.DO.AuthorDO;
import com.recSystem.Entity.DO.FavoriteDO;
import com.recSystem.Entity.DO.PaperDO;
import com.recSystem.Entity.DO.WorkDO;
import com.recSystem.Entity.DTO.*;
import com.recSystem.Entity.DTO.Param.ReceiveDataParam;
import com.recSystem.Entity.POJO.RecommendRes;
import com.recSystem.Mapper.AuthorMapper;
import com.recSystem.Mapper.FavoriteMapper;
import com.recSystem.Mapper.PaperMapper;
import com.recSystem.Mapper.WorkMapper;
import com.recSystem.Service.PaperService;
import com.recSystem.Service.RecommendService;
import com.recSystem.Utils.Constant;
import com.recSystem.Utils.HttpUtils;
import com.recSystem.Utils.RemoteCallUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Legolas_PRC
 * @date 2023/4/12 15:18
 **/
@Service
public class RecommendServiceImpl implements RecommendService {
    private static final Logger log= LoggerFactory.getLogger(RecommendServiceImpl.class);

    private final PaperMapper paperMapper;
    private final FavoriteMapper favoriteMapper;
    private final WorkMapper workMapper;
    private final AuthorMapper authorMapper;
    @Value("${recommend.num}")
    private int recNum;

    public RecommendServiceImpl( PaperMapper paperMapper, FavoriteMapper favoriteMapper, WorkMapper workMapper, AuthorMapper authorMapper) {
        this.paperMapper = paperMapper;
        this.favoriteMapper = favoriteMapper;
        this.workMapper = workMapper;
        this.authorMapper = authorMapper;
    }

    @Override
    public RecommendFavoriteDTO getRecommendByFavorite(int maxLength, int offset, int userId) {
        // userId获取收藏idlist
        List<FavoriteDO> favoriteDOS = favoriteMapper.selectList(new QueryWrapper<FavoriteDO>().eq("user_id", userId));
        List<Integer> paperIds=favoriteDOS.stream().map(FavoriteDO::getPaperId).collect(Collectors.toList());
//        RecommendRes<Integer> recommendRes = recommendByFavorite1(paperIds, recNum);
        RecommendRes<Integer> recommendRes = recommendByFavorite(paperIds, recNum);
        List<PaperDTO> paperDTOS = recommendRes.getRecData()
                .stream()
                .map(paperId -> this.getPaperDTOById(paperId, userId))
                .collect(Collectors.toList());

        return new RecommendFavoriteDTO(recNum,paperDTOS);
    }

    @Override
    public RecommendFavoriteDTO getRecommendByAuthor(int maxLength, int offset, int authorId, int userId) {
        List<WorkDO> workDOS = workMapper.selectList(new QueryWrapper<WorkDO>().eq("author_id", authorId));
        List<Integer> paperIds = workDOS.stream().map(WorkDO::getPaperId).collect(Collectors.toList());
//        RecommendRes<Integer> recommendRes = recommendByFavorite1(paperIds, recNum);
        RecommendRes<Integer> recommendRes = recommendByFavorite(paperIds, recNum);
        List<PaperDTO> paperDTOS = recommendRes.getRecData()
                .stream()
                .map(paperId -> this.getPaperDTOById(paperId, userId))
                .collect(Collectors.toList());


        return new RecommendFavoriteDTO(recNum,paperDTOS);
    }

    @Override
    public RecommendSimilarDTO getSimilarPapers(int paperId, int userId) {
//        RecommendRes<Integer> recRes = recommendBySignalPaper1(paperId, recNum);
        RecommendRes<Integer> recRes = recommendBySignalPaper(paperId, recNum);
        RecommendSimilarDTO recommendSimilarDTO = new RecommendSimilarDTO();
        recommendSimilarDTO.setPaper_title(paperMapper.selectById(paperId).getTitle());
        List<RecommendSimilarPaperDTO> recommendSimilarPaperDTOS=new ArrayList<>();
        PaperDTO paperDTO = new PaperDTO();
        // 保留小数点后两位
        DecimalFormat similar_score_format = new DecimalFormat("#.00");
        int count = 0;
        for (Integer recDatum : recRes.getRecData()) {
            RecommendSimilarPaperDTO recommendSimilarPaperDTO = new RecommendSimilarPaperDTO();
            recommendSimilarPaperDTO.setSimilar_score(Double.parseDouble(similar_score_format.format(recRes.getScoreList().get(count))));
            paperDTO = this.getPaperDTOById(recDatum,userId);
            recommendSimilarPaperDTO.setPaper_id(paperDTO.getPaper_id());
            recommendSimilarPaperDTO.setTitle(paperDTO.getTitle());
            recommendSimilarPaperDTO.setAuthors(paperDTO.getAuthors());
            recommendSimilarPaperDTO.setUpdate_date(paperDTO.getUpdate_date());
            recommendSimilarPaperDTO.setFavorite(paperDTO.isFavorite());
            recommendSimilarPaperDTOS.add(recommendSimilarPaperDTO);
            count ++;
        }
        recommendSimilarDTO.setPaperList(recommendSimilarPaperDTOS);
        return recommendSimilarDTO;
    }

    @Override
    public List<Integer> sortByScore(List<Integer> papers, int topK, int userId) {
        List<FavoriteDO> favoriteDOS = favoriteMapper.selectList(new QueryWrapper<FavoriteDO>().eq("user_id", userId));
        // 用户收藏的论文集合
        List<Integer> favoriteList = favoriteDOS.stream().map(FavoriteDO::getPaperId).collect(Collectors.toList());
        RecommendRes<Integer> recommendRes = recommendFromCandidate(userId, favoriteList, topK, papers);
        return recommendRes.getRecData();
    }

    // 根据userid,收藏列表,候选物品列表的ratingpredict推荐
    public RecommendRes<Integer> recommendFromCandidate(int userId,List<Integer> favoriteList,int topK,List<Integer> candidateList){
        JSONObject param =new JSONObject();
        param.put("task_id",3);
        param.put("task_type", Constant.QueryBased_REC);
        param.put("user_id",userId);
        param.put("item_id_list",favoriteList);
        param.put("candidate_item_list",candidateList);
        param.put("topk", topK);
        HttpUtils httpUtils = new HttpUtils();
        String res = httpUtils.doPostOrGet(param);
//        RemoteCallUtil remoteCallUtil = new RemoteCallUtil();
//        String res = remoteCallUtil.send(param);
        RecommendRes<Integer> recommendRes = null;

        if(res==null){
            log.error("===================获取推荐结果出错");
        }
        else{
            JSONObject jsonObject = JSON.parseObject(res);
            JSONArray recData = jsonObject.getJSONArray("rec_data");
            List<Integer> recDataList = recData.toJavaList(Integer.class);
            // List<List> scoreLists = scoreList.toJavaList(List.class);
            recommendRes = new RecommendRes<>();
            recommendRes.setRecData(recDataList);
        }
        return recommendRes;
    }

    public RecommendRes<Integer> recommendFromCandidate1(int userId,List<Integer> favoriteList,int topK,List<Integer> candidateList){
        // 从candidate里随机选topK个
        List<Integer> recData=new ArrayList<>();
        int size=candidateList.size();
        for(int i=0;i<topK;i++){
            recData.add(candidateList.get(size-i));
        }
        RecommendRes<Integer> recommendRes = new RecommendRes<>();
        recommendRes.setRecData(recData);
        return recommendRes;
    }

    // mock实现
//    public RecommendRes<Integer> recommendBySignalPaper1(int paperId, int topK){
//        List<Integer> recData = new ArrayList<>();
//        List<Integer> scoreList=new ArrayList<>();
//        for(int i=0;i<topK;i++){
//            recData.add(i+5);
//            scoreList.add(100-(i*i)/2);
//        }
//        return new RecommendRes<>(recData,scoreList);
//    }

    // 单篇论文余弦相似度推荐
    public RecommendRes<Integer> recommendBySignalPaper(int paperId, int topK){
        PaperDO paper = paperMapper.selectById(paperId);
        String categories = paper.getCategories();
        String title = paper.getTitle();
        List<String> categoryList = Arrays.asList(categories.split(" "));
        HashSet<Integer> candidateSet = new HashSet<>();
        HashSet<String> candidateNameSet = new HashSet<>();
        for (String item :  categoryList){
            QueryWrapper<PaperDO> qw =  new QueryWrapper<>();
            qw.like("categories", item);
            List<PaperDO> paperDOS = paperMapper.selectList(qw);
            for (PaperDO tempPaperDO : paperDOS){
                // 筛除自身和相同标题的论文
                if (tempPaperDO.getTitle().equals(title)){
                }else {
                    if (candidateNameSet.contains(tempPaperDO.getTitle())){
                    }else {
                        candidateNameSet.add(tempPaperDO.getTitle());
                        candidateSet.add(tempPaperDO.getPaperId());
                    }
                }
            }
        }
        JSONObject param =new JSONObject();
        param.put("task_id",1);
        param.put("task_type", Constant.SIGNAL_REC);
        param.put("item_id",paperId);
        param.put("topk", topK);
        param.put("candidate_item_list", candidateSet.stream().collect(Collectors.toList()));
//        RemoteCallUtil remoteCallUtil = new RemoteCallUtil();
//        String res = remoteCallUtil.send(param);
        RecommendRes<Integer> recommendRes = null;
        HttpUtils httpUtils = new HttpUtils();
        String res = httpUtils.doPostOrGet(param);
        if(res==null){
            log.error("===================获取推荐结果出错");
        }
        else{
            JSONObject jsonObject = JSON.parseObject(res);
            JSONArray recData = jsonObject.getJSONArray("rec_data");
            JSONArray scoreList = jsonObject.getJSONArray("score_list");
            recommendRes = new RecommendRes<>(recData.toJavaList(Integer.class), scoreList.toJavaList(Double.class));
        }
        return recommendRes;
    }
    // 多篇论文余弦相似度推荐（弃用）
//    public RecommendRes<List> recommendByMultiPaper(List<Integer> paperIds, int topK){
//        JSONObject param =new JSONObject();
//        param.put("task_id",2);
//        param.put("task_type", Constant.LIST_REC);
//        param.put("item_id_list",paperIds);
//        param.put("topk", topK);
////        RemoteCallUtil remoteCallUtil = new RemoteCallUtil();
////        String res = remoteCallUtil.send(param);
//        RecommendRes<List> recommendRes = null;
//        HttpUtils httpUtils = new HttpUtils();
//        String res = httpUtils.doPostOrGet(param);
//
//        if(res==null){
//            log.error("===================获取推荐结果出错");
//        }
//        else{
//            JSONObject jsonObject = JSON.parseObject(res);
//            JSONArray recData = jsonObject.getJSONArray("rec_data");
//            JSONArray scoreList = jsonObject.getJSONArray("score_list");
//            List<List> recDataList = recData.toJavaList(List.class);
//            List<List> scoreLists = scoreList.toJavaList(List.class);
//            recommendRes = new RecommendRes<>(recDataList, scoreLists);
//        }
//        return recommendRes;
//    }
    // 按收藏推荐
    public RecommendRes<Integer> recommendByFavorite(List<Integer> paperIds,int topK){
        HashSet<String> favoriteCategorySet = new HashSet<>();
        HashSet<String> favoriteTitleSet = new HashSet<>();
        for (Integer favoritePaperId : paperIds){
            PaperDO tempPaper = paperMapper.selectById(favoritePaperId);
            String categories = tempPaper.getCategories();
            favoriteTitleSet.add(tempPaper.getTitle());
            List<String> tempCategoryList = Arrays.asList(categories.split(" "));
            for (String item : tempCategoryList){
                favoriteCategorySet.add(item);
            }
        }
        HashSet<Integer> candidateSet = new HashSet<>();
        HashSet<String> candidateNameSet = new HashSet<>();
        for (String item : favoriteCategorySet){
            QueryWrapper<PaperDO> qw =  new QueryWrapper<>();
            qw.like("categories", item);
            List<PaperDO> paperDOS = paperMapper.selectList(qw);
            for (PaperDO tempPaperDO : paperDOS){
                // 筛除自身和相同标题的论文
                if (favoriteTitleSet.contains(tempPaperDO.getTitle())){
                }else {
                    if (candidateNameSet.contains(tempPaperDO.getTitle())){
                    }else {
                        candidateNameSet.add(tempPaperDO.getTitle());
                        candidateSet.add(tempPaperDO.getPaperId());
                    }
                }
            }
        }
        JSONObject param =new JSONObject();
        param.put("task_id",2);
        param.put("task_type", Constant.FAVORITE_REC);
        param.put("item_id_list",paperIds);
        param.put("candidate_item_list", candidateSet.stream().collect(Collectors.toList()));
        param.put("topk", topK);
//        RemoteCallUtil remoteCallUtil = new RemoteCallUtil();
//        String res = remoteCallUtil.send(param);
        RecommendRes<Integer> recommendRes = null;
        HttpUtils httpUtils = new HttpUtils();
        String res = httpUtils.doPostOrGet(param);

        if(res==null){
            log.error("===================获取推荐结果出错");
        }
        else{
            JSONObject jsonObject = JSON.parseObject(res);
            JSONArray recData = jsonObject.getJSONArray("rec_data");
            List<Integer> recDataList = recData.toJavaList(Integer.class);
            recommendRes = new RecommendRes<>();
            recommendRes.setRecData(recDataList);
        }
        return recommendRes;
    }
    // mock实现
    public RecommendRes<Integer> recommendByFavorite1(List<Integer> paperIds,int topK){
        List<Integer> recData = new ArrayList<>();
        for(int i=0;i<topK;i++){
            recData.add(i+5);
        }
        RecommendRes<Integer> recommendRes = new RecommendRes<>();
        recommendRes.setRecData(recData);
        return recommendRes;
    }

    /**
     * 根据论文id返回论文基本信息
     *
     * @param paperId 论文id
     * @param userId
     * @return 返回PaperDTO, 包含作者和基本信息
     */
    public PaperDTO getPaperDTOById(Integer paperId, Integer userId) {
        PaperDO paperDO = paperMapper.selectById(paperId);
        return convertToDTO(paperDO, userId);
    }

    /**
     * 根据论文DO获取DTO，包含作者
     *
     * @param paper  论文DO对象
     * @param userId
     * @return 论文DTO对象
     */
    public PaperDTO convertToDTO(PaperDO paper, Integer userId) {
        // 将paperDO转成paperDTO
        PaperDTO paperDTO = PaperConverter.Instance.paperDOToDto(paper);
        int paperId = paper.getPaperId();
        // AuthorService.getAuthorDTOsByPaperId(int paperId);
        List<AuthorDTO> authorDTOS = getAuthorsByPaperId(paperId);
        paperDTO.setAuthors(authorDTOS);

        // TODO 可以优化，查找收藏的论文时可以略去这个查询
        // 得到论文是否被收藏
        // boolean isFavorite = favoriteService.isFavorite(userId, paperId);
        paperDTO.setFavorite(isFavorite(userId, paperId));
        return paperDTO;
    }

    /**
     * 根据论文id获得该论文的全部作者集合
     *
     * @param paperId
     * @return
     */
    public List<AuthorDTO> getAuthorsByPaperId(Integer paperId) {
        // 要根据论文id得到作者DTO集合
        // 先根据论文id得到创作关系集合
        List<WorkDO> workRelationList = workMapper.selectList(new QueryWrapper<WorkDO>().eq("paper_id", paperId));
        // 根据创作关系得到作者集合
        List<AuthorDO> authors = workRelationList.stream()
                .map(workRelation -> authorMapper.selectById(workRelation.getAuthorId()))
                .collect(Collectors.toList());
        // 把authorDO映射成AuthorDTO
        List<AuthorDTO> authorDTOs = authors.stream().map(authorDO -> {
            AuthorConverter instance = AuthorConverter.INSTANCE;
            return instance.authorDoToDto(authorDO);
        }).collect(Collectors.toList());

        return authorDTOs;
    }

    /**
     * 判断该论文是否被当前用户收藏
     * TODO 我在想这里是不是可以返回收藏id
     *
     * @param userId
     * @param paperId
     * @return 被收藏则返回id, 否则返回-1
     */
    public boolean isFavorite(Integer userId, Integer paperId) {
        HashMap<String, Integer> queryMap = new HashMap<>(2);
        queryMap.put("user_id", userId);
        queryMap.put("paper_id", paperId);
        return favoriteMapper.exists(new QueryWrapper<FavoriteDO>().allEq(queryMap));
    }

}
